import axios from 'axios'
import utils from '../helpers/utils'
import wxApiLogger from './WxApiLogger'

type APIQueryArgCollection = ApiTypes.APIBodyArgCollection
type APIBodyArgCollection = ApiTypes.APIBodyArgCollection
type WxApiBaseResult = ApiTypes.WxApiBaseResult
type HTTPStatusCode = ApiTypes.HTTPStatusCode

export class ApiProxy<Result = {}> {
  public static mockError = false
  private method: 'GET' | 'POST'
  private path: string
  private headers: { [name: string]: string } = {}
  private urlArgs?: APIQueryArgCollection
  private bodyArgs?: APIBodyArgCollection
  private host: string = 'https://api.weixin.qq.com'
  private wxapiTime!: { start: number, end: number }

  // callbacks
  private onSuccess: null | ((result: Result & WxApiBaseResult) => true | void) = null
  private onFail: null | ((data: WxApiBaseResult) => true | void) = null
  private onAlways: null | (() => void) = null

  constructor(
    method: 'GET' | 'POST',
    path: string,
    urlArgs?: APIQueryArgCollection,
    body?: APIBodyArgCollection
  ) {
    this.method = method
    this.path = path
    this.urlArgs = urlArgs
    if (method === 'POST') {
      this.bodyArgs = body
    } else {
      if (body) {
        console.error('body:', body)
        throw new Error(`GET api call ${path} shouldn't have a body`)
      }
    }
    setTimeout(() => {
      this.send()
    }, 0)
  }
  /**
   * 设置api成功回调
   * @param callback 成功回调
   */
  public success(callback: (result: Result & WxApiBaseResult) => true | void): this {
    this.onSuccess = callback
    return this
  }

  /**
   * 设置 api 失败回调，会在 http 请求不成功时触发
   * @param callback 失败回调，若返回 true 则会阻止默认的 alert 行为
   */
  public fail(callback: (data: WxApiBaseResult) => true | void): this {
    this.onFail = callback
    return this
  }

  /**
   * 设置 api 结束回调，不论 api 成功或失败，都会调用此回调。适用场景是一些不需关心 api 本身是否失败的场景（比如按钮解锁）
   * @param callback 回调函数。函数没有参数，如果需要参数，应当使用其他的方法
   */
  public always(callback: () => void): this {
    this.onAlways = callback
    return this
  }

  /**
   * 设置请求 header
   * @param name 名称
   * @param value 值
   */
  public setHeader(name: string, value: string): this {
    this.headers[name] = value
    return this
  }

  private send() {
    if (this.method === 'GET') {
      this.sendGet()
    } else {
      this.sendPost()
    }
  }

  private getAuthHeaders(): { [key: string]: string } {
    return {}
  }

  private updateRequestTime(end?: number) {
    this.wxapiTime = {
      start: Date.now(),
      end: end || 0
    }
  }

  private async sendGet() {
    const headers = this.getAuthHeaders()
    for (const key in this.headers) {
      headers[key] = this.headers[key]
    }

    try {
      this.updateRequestTime()
      const res = await axios.request({
        baseURL: this.host,
        url: this.path,
        method: this.method,
        headers,
        params: utils.underlize(this.urlArgs as APIQueryArgCollection)
      })
      this.updateRequestTime(Date.now())
      const interval = this.wxapiTime.end - this.wxapiTime.start
      this.handleRes(true, res.status as HTTPStatusCode, res.data, interval)
    } catch (err) {
      this.handleRes(false, err, {}, 0)
    }
  }

  private async sendPost() {
    const headers = this.getAuthHeaders()
    for (const key in this.headers) {
      headers[key] = this.headers[key]
    }
    headers['Content-Type'] = 'application/json'
    try {
      this.updateRequestTime()
      const res = await axios.request({
        method: this.method,
        baseURL: this.host,
        url: this.path,
        headers,
        data: this.bodyArgs,
        params: this.urlArgs
      })
      this.updateRequestTime(Date.now())
      const interval = this.wxapiTime.end - this.wxapiTime.start
      this.handleRes(true, res.status as HTTPStatusCode, res.data, interval)
    } catch (err) {
      this.handleRes(false, err, {}, 0)
    }
  }

  private handleRes(
    reqSuccess: boolean,
    httpStatusCode: HTTPStatusCode,
    data: object,
    requestTime: number
  ) {
    let parsedData = utils.camelize(data) as Result & WxApiBaseResult
    if (!reqSuccess) {
      wxApiLogger.log('error', `[ HTTP请求本身失败(超时, 断网)] HTTP Error ${httpStatusCode} for api call ${this.path}`)
      if (this.onFail) {
        this.onFail({errcode: 50050, errmsg: 'http 请求失败'})
      }
    } else {
      const { errcode, errmsg } = parsedData
      if (errcode && errcode !== 0) {
        wxApiLogger.log('error', `[ Wx Api Call Error] HTTP Error ${httpStatusCode} ErrorCode: ${errcode}; ErrorMsg: ${errmsg}; Url: ${this.path} Time ${requestTime}ms`)
        if (this.onFail) {
          this.onFail(parsedData)
        }
      } else {
        if (this.onSuccess) {
          try {
            parsedData.errcode = 0
            parsedData.errmsg = 'ok'
            this.onSuccess(parsedData)
          } catch (error) {
            wxApiLogger.log('error', `[ Api Success Callback Error for ] ${this.path}: ${error} Time ${requestTime}ms`)
          }
        }
      }
    }
    if (this.onAlways) {
      return this.onAlways()
    }
  }
}

export default ApiProxy
